package cn.ren.cloud.service.impl;

import cn.ren.cloud.domain.PayFlow;
import cn.ren.cloud.domain.PayOrder;
import cn.ren.cloud.dto.AlipayNotifyDto;
import cn.ren.cloud.dto.PayApplyDto;
import cn.ren.cloud.dto.PayTxLocalHandlerDto;
import cn.ren.cloud.enums.GlobelExceptionEnum;
import cn.ren.cloud.exception.GlobalCustomException;
import cn.ren.cloud.mapper.PayOrderMapper;
import cn.ren.cloud.properties.AliPayProperties;
import cn.ren.cloud.service.IPayFlowService;
import cn.ren.cloud.service.IPayOrderService;
import cn.ren.cloud.utils.AssertUtil;
import com.alibaba.fastjson.JSON;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author Ren
 * @since 2023-01-10
 */
@Service
@Slf4j
public class PayOrderServiceImpl extends ServiceImpl<PayOrderMapper, PayOrder> implements IPayOrderService {

    private final AliPayProperties aliPayProperties;
    private final RocketMQTemplate rocketMQTemplate;
    private final IPayFlowService payFlowService;

    public PayOrderServiceImpl(AliPayProperties aliPayProperties, RocketMQTemplate rocketMQTemplate, IPayFlowService payFlowService) {
        this.aliPayProperties = aliPayProperties;
        this.rocketMQTemplate = rocketMQTemplate;
        this.payFlowService = payFlowService;
    }

    /**
     * 查询订单号
     *
     * @param orderNo
     * @return
     */
    @Override
    public PayOrder selectByOrderNo(String orderNo) {
        Wrapper<PayOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no", orderNo);
        return selectOne(wrapper);
    }

    /**
     * 调用支付接口
     *
     * @param payApplyDto
     * @return
     */
    @Override
    public String apply(PayApplyDto payApplyDto) {
        String orderNo = payApplyDto.getOrderNo();
        String callUrl = payApplyDto.getCallUrl();
        // 2.业务校验
        // 2.1.判断支付单是否成功
        PayOrder payOrder = this.selectByOrderNo(orderNo);
        AssertUtil.isNotNull(payOrder, GlobelExceptionEnum.GLOBE_EXCEPTION_ENUM_PARAM_ERROR);
        // 2.2.判断支付单状态是否为待支付
        AssertUtil.isTrue(PayOrder.WAIT_PAY.equals(payOrder.getPayStatus()), GlobelExceptionEnum.GLOBE_EXCEPTION_ENUM_PARAM_ERROR);

        // 3.发起支付 策略模式
        if (PayOrder.ALI_PAY.equals(payOrder.getPayType())) {
            return aliPay(payOrder, callUrl);
        } else if (PayOrder.WX_PAY.equals(payOrder.getPayType())) {

        } else if (PayOrder.BANK_PAY.equals(payOrder.getPayType())) {

        }
        throw new GlobalCustomException(GlobelExceptionEnum.GLOBE_EXCEPTION_ENUM_PARAM_ERROR);
    }

    /**
     * 支付宝异步回调
     *
     * @param alipayNotifyDto
     * @return
     */
    @Override
    public String callback(AlipayNotifyDto alipayNotifyDto) {
        try {
            String appId = alipayNotifyDto.getApp_id();
            String totalAmount = alipayNotifyDto.getTotal_amount();
            String outTradeNo = alipayNotifyDto.getOut_trade_no();
            String tradeStatus = alipayNotifyDto.getTrade_status();
            // 1.业务校验
            Map map = JSON.parseObject(JSON.toJSONString(alipayNotifyDto), Map.class);
            Boolean isVerify = Factory.Payment.Common().verifyNotify(map);
            if (!isVerify) {
                // 忽悠黑客以为我们上当了
                return "success";
            }
            // 2.验证appId
            if (!aliPayProperties.getAppId().equals(appId)) {
                return "success";
            }
            // 3.校验orderNo
            PayOrder payOrder = selectByOrderNo(outTradeNo);
            if (payOrder == null) {
                return "success";
            }
            // 4.返回-1不相等
            if (payOrder.getAmount().compareTo(new BigDecimal(totalAmount)) == -1) {
                return "success";
            }
            // 5.判断支付宝传递的支付状态是否是已完成
            if (!AlipayNotifyDto.TRADE_SUCCESS.equals(tradeStatus)) {
                return "fail";
            }
            // 6.校验我们本地支付单状态是否正确
            Integer payStatus = payOrder.getPayStatus();
            if (PayOrder.SUCCESS_PAY.equals(payStatus)) {
                return "success";
            }
            if (PayOrder.CANCEL_PAY.equals(payStatus) || PayOrder.FAIL_PAY.equals(payStatus)) {
                // 6.调用退款接口
                AlipayTradeRefundResponse response = Factory.Payment.Common().refund(outTradeNo, totalAmount);
                String code = response.getCode();
                if (!"10000".equals(code)) {
                    log.error("发起退款失败，订单号为：{}，状态码为：{}, 退款金额为：{}", outTradeNo, code, totalAmount);
                }
            }
            if (!PayOrder.WAIT_PAY.equals(payStatus)) {
                // 7.通知管理员
                log.error("订单状态异常，订单号为：{}，订单状态为：{}, 退款金额为：{}", outTradeNo, payStatus, totalAmount);
            }
            // 8.修改支付单状态、保存支付流水、发送事务消息
            PayTxLocalHandlerDto payTxLocalHandlerDto = initPayflow(payOrder);
            String extParams = payOrder.getExtParams();
            Message<String> message = MessageBuilder.withPayload(extParams).build();
            TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(
                    "pay-order-group",
                    "pay-order-topic:pay-order-tag",
                    message,
                    payTxLocalHandlerDto
            );
            SendStatus sendStatus = sendResult.getSendStatus();
            if (!SendStatus.SEND_OK.equals(sendStatus)) {
                log.error("订单发送事务消息失败，订单号为：{}，状态为：{}, 退款金额为：{}", outTradeNo, sendStatus, totalAmount);
                return "fail";
            }
            Factory.Payment.Common().cancel(outTradeNo);
            return "success";
        } catch (Exception e) {
            e.printStackTrace();
            return "fail";
        }
    }

    /**
     * 支付结果处理程序
     *
     * @param payTxLocalHandlerDto
     */
    @Override
    public void payResultHandler(PayTxLocalHandlerDto payTxLocalHandlerDto) {
        PayFlow payFlow = payTxLocalHandlerDto.getPayFlow();
        PayOrder payOrder = payTxLocalHandlerDto.getPayOrder();
        // 2.修改支付单
        updateById(payOrder);
        // 3.保存订单流水
        payFlowService.insert(payFlow);
    }

    /**
     * 初始化支付
     *
     * @param payOrder
     * @return
     */
    private PayTxLocalHandlerDto initPayflow(PayOrder payOrder) {
        payOrder.setPayStatus(PayOrder.SUCCESS_PAY);
        PayFlow payFlow = new PayFlow();
        Date date = new Date();
        payFlow.setNotifyTime(date);
        payFlow.setSubject(payOrder.getSubject());
        payFlow.setOutTradeNo(payOrder.getOrderNo());
        payFlow.setTotalAmount(payOrder.getAmount());
        payFlow.setTradeStatus(PayFlow.SUCCESS_PAY.toString());
        return new PayTxLocalHandlerDto(payOrder, payFlow);
    }

    /**
     * ali支付接口
     *
     * @param payOrder
     * @param callUrl
     * @return
     */
    private String aliPay(PayOrder payOrder, String callUrl) {
        try {
            // 1. 发起网站支付调用
            String orderNo = payOrder.getOrderNo();
            AlipayTradePagePayResponse response = Factory.Payment.Page().pay(payOrder.getSubject(), orderNo, payOrder.getAmount().toString(), callUrl);
            // 2. 处理响应或异常
            if (ResponseChecker.success(response)) {
                log.info("调用支付成功，订单号为：" + orderNo);
                return response.getBody();
            } else {
                log.error("调用失败，原因：" + response.getBody() + "，" + response.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("调用遭遇异常，原因：" + e.getMessage());
            throw new GlobalCustomException(GlobelExceptionEnum.GLOBE_EXCEPTION_ENUM_PARAM_ERROR);
        }
        return "";
    }
}
